#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <Defines.h>
#include <Kademlia.h>
#include <Prefs.h>
#include "SearchManager.h"
#include <KademliaUDPListener.h>
#include <RoutingZone.h>
#include <Contact.h>
#include <OPCodes.h>
#include <UInt128.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//#define PR_DEBUG 0 //for no output, total silence
#define PR_DEBUG 1 //all output enabled
//#define PR_DEBUG 5 //only node search output, for planet lab.

#define USE_RANDOM_HASH_ID 1

#define time(NULL)  KadClog_get_current_dvn_time()

using namespace std;

CKademlia::~CKademlia() {
  delete mySearchMan;
  delete m_prefs;
  delete m_routingZone;
  delete m_udpListener;
  if (myUDP != NULL) {
    free(myUDP);
  }
}

void CKademlia::start(char * arg1, char *arg2, int hashtype)
{
	
//Uncomment this below if you want a user defined hash
//but MAKE sure to comment out the other call to start()

#ifdef USE_RANDOM_HASH_ID
  //use a random hash
  start(new CPrefs(domain2hip(arg1)), arg1,arg2);//, curNodeM); 
#else
  unsigned char *myhash;
  myhash = (unsigned char *) malloc(sizeof(unsigned char) * 33);

  if(hashtype == 1) {
    if ( string2int128(myhash, "deadbeefbaadd00ddeadbeefbaadd00d") == false ) {
      KadC_logt(ERROR_PRINT,"problem setting up hash\n");
    }
    start(new CPrefs(myhash), arg1,arg2);
  }
  
  else {
    if ( string2int128(myhash, "6fadbeefbaadd00ddeadbeefbaadd00d") == false ) {
      KadC_logt(ERROR_PRINT,"problem setting up hash\n");
    }
    start(new CPrefs(myhash), arg1,arg2);
  }
#endif
}

void CKademlia::start(CPrefs *prefs, char * arg1, char *arg2)
{
	int status;
	unsigned long int ip;
	int port;
	//CKademlia::restoreDVNState(curNodeMem);//For now, copy all the memory back into the static variables
	
	if( m_running ) {
		//SDRIlog(g_cbif, 1, "Cannot start Kad node, it's already running.\n"); 
		delete prefs;
		return;
	}
	if( prefs == NULL ){
		//SDRIlog(g_cbif, 1, "Cannot start Kad node, prefs is NULL.\n"); 
	
	}
	
	//KadC_logt(GENERAL_PRINT,"Starting new kad instance\n");
	//SDRIlog(g_cbif, 1, "Starting Kad node with ip: %s:%s\n",arg1, arg2); 


	//For DVN, we assume that the simulation always start at time = 0
	m_nextSearchJumpStart = time(NULL);
	m_nextSelfLookup = time(NULL) + MIN2S(3);
	m_statusUpdate = time(NULL);
	m_bigTimer = time(NULL);
	m_nextFirewallCheck = time(NULL) + (HR2S(1));
	// First Firewall check is done on connect, find a buddy after the first 10min of starting
	// the client to try to allow it to settle down..
	m_nextFindBuddy = time(NULL) + (MIN2S(5));
	m_nextRoutingTableDump = time(NULL) +  (MIN2S(5));
	m_bootstrap = 0;
	srand((uint32)time(NULL) + atoi(arg1));
	//instance = thisNode->instance;//new CKademlia();	

	bytes_sent = 0;
	packets_sent = 0;
	m_prefs = prefs;
	m_udpListener = NULL;
	m_routingZone = NULL;
	myUDP = NULL;

	//instance->m_indexed = new CIndexed();
	m_routingZone = new CRoutingZone(this);
	m_udpListener = new CKademliaUDPListener(this);
	
	/* start the UDPIO stuff */
	ip = domain2hip(arg1);
	if(ip == 0xffffffff) {
		KadC_logt(ERROR_PRINT, "Invalid local ip: %s\n",arg1);
		//SDRIlog(g_cbif, 1, "Invalid local ip: %s\n",arg1); 
	}
	port = atoi(arg2);
	myUDP = (UDPIO *) malloc(sizeof(UDPIO));
	if(myUDP == NULL) {
		KadC_logt(ERROR_PRINT,"Malloc failed\n");
		//SDRIlog(g_cbif, 1, "malloc failed!\n"); 
		exit(1);
	}
	
	memset(myUDP, 0, sizeof(UDPIO));
	myUDP->bufsize = 2048;
	myUDP->localip = ip;
	myUDP->localport = port;
	status = startUDPIO(myUDP);

	m_prefs->setIpString(std::string(arg1));
	if (status == 0) {
	  /*KadC_logt(VERBOSE_PRINT,"Network ok.\n");*/
		//SDRIlog(g_cbif, 1, "Network started.\n"); 
		
		m_running = true;
		m_prefs->SetUDPPort(port);
		m_prefs->setIPAddress(ip);
		m_prefs->SetPort(port+1024);
	}
	else {
		KadC_logt(ERROR_PRINT,"Error starting Network\n");
		//SDRIlog(g_cbif, 1, "Error starting UDP layer.\n"); 
		m_running = false;
	}
}

//[dfk] This is never called.  Find out why
void CKademlia::stop()
{	
	if( m_running ) {
		return;
	}
	
	KadC_logt(GENERAL_PRINT,"Stopping Kademlia\n");
	m_running = false;
	
	//CSearchManager::stopAllSearches();
	delete m_udpListener;
	m_udpListener = NULL;
	
	delete m_routingZone;
	m_routingZone = NULL;
	
	//delete instance->m_indexed;
	//instance->m_indexed = NULL;
	
	delete m_prefs;
	m_prefs = NULL;
	
	m_events.clear();

	//delete this;
}

//////////////////////////////////////////////////
//
//  This used to be called once per (simulation) second
//  Modified to be called only when we are sure that there is something to process.
//  Sets the amount of time until the next event
//
//////////////////////////////////////////////////
void CKademlia::process()
{
  //Gets the current time
  time_t now = time(NULL);
  time_t soonest_event = time(NULL) + HR2S(1);//Start at 1 hour from now, then select the soonest occurring event


  // Sets the next timer event to be the lowest possible. 
  // It will "float" up when we look at the earliest next event. 
  sleep_until = 1;
	
  if( !m_running){
    return;//If there is a problem, we abort
  }
  
  //printf("Current time: %2d\n", now);
  //printf("m_bigTimer: %2d\n", m_bigTimer);
  //KadC_logt(GENERAL_PRINT,"%i: dvn time: %i, local time:%i\n", myUDP->localip, KadClog_get_current_dvn_time(), now);
  

  CRoutingZone *zone;
  EventMap::const_iterator it;
  uint32 maxUsers = 0;
  uint32 tempUsers = 0;
  uint32 lastContact = 0;
  bool updateUserFile = false;
  
  lastContact = m_prefs->getLastContact();
  
  
  if( m_nextFirewallCheck <= now) {
	  RecheckFirewalled();
  }
  if (m_nextSelfLookup <= now) {
    KadC_logt(FINDNODE_PRINT,"performing a self lookup!\n");
    
    //CSearchManager::findNodeComplete(instance->m_prefs->getKadID());
    mySearchMan->findNodeComplete(m_prefs->getKadID());
    m_nextSelfLookup = HR2S(4) + now;
  }
  if (m_nextFindBuddy <= now) {
    m_prefs->setFindBuddy();
    m_nextFindBuddy = MIN2S(5) + m_nextFirewallCheck;
  }
  //KadC_logt(GENERAL_PRINT,"%i processing events...\n");
  for (it = m_events.begin(); it != m_events.end(); it++) {
    zone = it->first;
    if( updateUserFile ) {
      tempUsers = zone->estimateCount();
      if( maxUsers < tempUsers ) {
	maxUsers = tempUsers;
      }
    }
    if (m_bigTimer <= now) {
      if( zone->m_nextBigTimer <= now ) {
	if(zone->onBigTimer()) {
	  zone->m_nextBigTimer = HR2S(1) + now;
	  m_bigTimer = SEC(10) + now;
	}
      } else {
	if( lastContact && ( (now - lastContact) > (KADEMLIADISCONNECTDELAY-MIN2S(5)))) {
	  if(zone->onBigTimer()) {
	    zone->m_nextBigTimer = HR2S(1) + now;
	    m_bigTimer = SEC(10) + now;
	  }
	} 
      }
    }
    
    if (zone->m_nextSmallTimer <= now) {
      zone->onSmallTimer();
      zone->m_nextSmallTimer = MIN2S(1) + now;
    }
    // This is a convenient place to add this, although not related to routing
    
    if (m_nextSearchJumpStart <= now) {
      //CSearchManager::jumpStart();
      mySearchMan->jumpStart();
      m_nextSearchJumpStart += SEARCH_JUMPSTART;
    }
    
    
    //KadC_logt(GENERAL_PRINT, "Total contacts = %i\n", getRoutingZone()->getNumContacts() );
    
  }
  
  it = m_events.begin();
	
  /* Do needed merges if there was a successful deletion */
  while (it != m_events.end()) {
    zone = it->first;
    ++it;
    if (zone->IsDirty()) {
      zone->merge();
      it = m_events.begin(); /* restart */
    }
  }
  //Update user count only if changed.
  //KadC_logt(GENERAL_PRINT,"%i processing done\n");
  
  
  // Go through all the time checks and find the one closest to expiration
  // Set the next event to that time
  if( m_nextFirewallCheck <= soonest_event) {
    soonest_event =  m_nextFirewallCheck;
  }
  if (m_nextSelfLookup <= soonest_event) {
    soonest_event = m_nextSelfLookup;
  }
  if (m_nextFindBuddy <= soonest_event) {
    soonest_event = m_nextFindBuddy;
  }
    
  //printf("Sleep timer: %i\n", sleep_until);
  // Don't sleep more than 10 minutes
  if(/*(sleep_until > 600) || */(sleep_until < 0))
    sleep_until = 0;
  
  if(m_nextRoutingTableDump <= now)
    {
      //Time to dump the routing table to stdout
      //Every 5 mins, print out the routing table
      if((myUDP != NULL) && (getRoutingZone() != NULL))
	{
	  struct in_addr n_addr;
	  char* a_addr = NULL;
	  n_addr.s_addr = htonl(myUDP->localip);
	  a_addr = inet_ntoa(n_addr);
	  KadC_logt(RTABLE_PRINT, "%s: Total contacts = %i, contacts:", a_addr, getRoutingZone()->getNumContacts() );
	  getRoutingZone()->dumpRoutingTable();
	  KadC_log(RTABLE_PRINT, "\n");
	}
      m_nextRoutingTableDump = MIN2S(60) + now;
    }
  
}	

void CKademlia::addEvent(CRoutingZone *zone)
{
	m_events[zone] = zone;
}

void CKademlia::removeEvent(CRoutingZone *zone)
{
	m_events.erase(zone);
}

bool CKademlia::isConnected(void)
{
	if( m_prefs ) {
		return m_prefs->hasHadContact();
	}
	return false;
}

bool CKademlia::isFirewalled(void)
{
	if( m_prefs ) {
		return m_prefs->getFirewalled();
	}
	return true;
}

uint32 CKademlia::getKademliaUsers(void)
{
	if( m_prefs ) {
		return m_prefs->getKademliaUsers();
	}
	return 0;
}

uint32 CKademlia::getKademliaFiles(void)
{
	if( m_prefs ) {
		return m_prefs->getKademliaFiles();
	}
	return 0;
}

uint32 CKademlia::getTotalStoreKey(void)
{
	if( m_prefs ) {
		return m_prefs->getTotalStoreKey();
	}
	return 0;
}

uint32 CKademlia::getTotalStoreSrc(void)
{
	if( m_prefs ) {
		return m_prefs->getTotalStoreSrc();
	}
	return 0;
}

uint32 CKademlia::getTotalStoreNotes(void)
{
	if( m_prefs ) {
		return m_prefs->getTotalStoreNotes();
	}
	return 0;
}

uint32 CKademlia::getTotalFile(void)
{
	if( m_prefs ) {
		return m_prefs->getTotalFile();
	}
	return 0;
}

uint32 CKademlia::getIPAddress(void)
{
	if( m_prefs ) {
		return m_prefs->getIPAddress();
	}
	return 0;
}

void CKademlia::processPacket(const byte *data, uint32 lenData, uint32 ip, uint16 port)
{
  if( m_udpListener ) {
    m_udpListener->processPacket( data, lenData, ip, port);
  }
}

bool CKademlia::getPublish(void)
{
	if( m_prefs ) {
		return m_prefs->getPublish();
	}
	return 0;
}

void CKademlia::bootstrap(uint32 ip, uint16 port)
{
  KadC_logt(GENERAL_PRINT, "time - bootstrap %d\n", (time(NULL) - m_bootstrap));
  if( m_udpListener && !isConnected() &&  ((time(NULL) - m_bootstrap) > MIN2S(1)) ) 
    {
		m_udpListener->bootstrap( ip, port);
	}
}

void CKademlia::RecheckFirewalled()
{
	if( getPrefs() ) {
		m_prefs->setFindBuddy(false);
		m_prefs->setRecheckIP();
		m_nextFindBuddy = MIN2S(5) + m_nextFirewallCheck;
		m_nextFirewallCheck = HR2S(1) +  time(NULL);
	}
}

CPrefs *CKademlia::getPrefs(void)
{
  /*
	if (instance == NULL || instance->m_prefs == NULL) {
		//wxASSERT(0);
	  printf("CKademlia Error!  Preferences is NULL\n");
		return NULL;
	}
	return instance->m_prefs;
  */
	if (m_prefs == NULL) {
		//wxASSERT(0);
    KadC_logt(ERROR_PRINT, "CKademlia Error!  Preferences is NULL\n");
		return NULL;
	}
	return m_prefs;
}
UDPIO *CKademlia::getUDPIO(void)
{
	if (myUDP == NULL) {
		return NULL;
	}
	
	return myUDP;
}


CKademliaUDPListener *CKademlia::getUDPListener(void)
{
	if (m_udpListener == NULL) {
		//wxASSERT(0);
		return NULL;
	}
	return m_udpListener;
}


CRoutingZone *CKademlia::getRoutingZone(void)
{
	if (m_routingZone == NULL) {
		//wxASSERT(0);
		return NULL;
	}
	return m_routingZone;
}

// Global function.

bool CKademlia::string2int128(unsigned char *i128, char *s) {
	int i, n;
	unsigned int u;
	if(s == NULL)				/* NULL string */
		return false;	/* error */
	
	if(((n=strlen(s))&1) != 0 ) {	/* odd length string */
		n--;
	}

for(i=0; i<16; i++)
i128[i] = 0;

for(i=0; i<16 && n > 0; i++, s += 2, n -= 2) {
	if(sscanf(s, "%2x", &u ) != 1)
		return false;	/* invalid hex char */
	i128[i] = u;
}
return true;	/* OK */
}


void CKademlia::sendPacket(unsigned char* data, uint32 lenData, uint32 remoteip, uint16 remoteport) {
	int status;
	unsigned long int newsize;
	//track stats
	packets_sent++;
	bytes_sent += lenData;
	
	if( lenData >= 700) {
		newsize = m_udpListener->PackPacket(data, lenData); 
		if(newsize == 0) {
			KadC_logt(ERROR_PRINT,"problem packing packet, sending unpacked!\n");
			status = UDPsend(myUDP,data,lenData,remoteip,remoteport);
		}
		else {
			KadC_logt(GENERAL_PRINT,"Successfully packed packet, old size %i new size %i \n",lenData, newsize);
			status = UDPsend(myUDP,data,newsize,remoteip,remoteport);
		}
	}
	else {
		status = UDPsend(myUDP,data,lenData,remoteip,remoteport);
	}
	
	//check status of send
	if(status > 0) {
	  //KadC_logt(GENERAL_PRINT,"Successful send\n");
		
	}
	else  {
		KadC_logt(ERROR_PRINT,"error sending packet\n");
		
	}
	
}

bool CKademlia::getPacket() {
	
	if( m_udpListener )  {
		return m_udpListener->getPacket(myUDP);
	}
	return false;
}


char *CKademlia::h_to_a(unsigned long int ip) {
	return htoa(ip);
}

///////////////////////////////////////
//
//  [dfk]  Restores the state of a node from the memory pointer given by DVN.
//         Ideally, we do this at the beginning of every function to make sure that all the
//         Static variables are set correctly
//
//         Alternatively, we could also modify the variables to always use the struct instance.
//
//  [dfk]  DEPRECATED!!! DVN does this automatically now
//
///////////////////////////////////////
/*
void CKademlia::restoreDVNState(void* curNodeMem)
 {
	struct kad_node_mem_t *thisNode = (struct kad_node_mem_t*)curNodeMem;
	
	instance = thisNode->instance;
	//m_events;
	m_nextSearchJumpStart = thisNode->m_nextSearchJumpStart;
	m_nextSelfLookup =      thisNode->m_nextSelfLookup;
	m_nextFirewallCheck =   thisNode->m_nextFirewallCheck;
	m_nextFindBuddy =       thisNode->m_nextFindBuddy;
	m_statusUpdate =        thisNode->m_statusUpdate;
	m_bigTimer =            thisNode->m_bigTimer;
	m_bootstrap =           thisNode->m_bootstrap;
	m_running =             thisNode->m_running;
	bytes_sent =            thisNode->bytes_sent;
	packets_sent =          thisNode->packets_sent;
	//m_prefs =               thisNode->m_prefs;
	m_routingZone =         thisNode->m_routingZone;
	m_udpListener =         thisNode->m_udpListener;
	//UDPIO		myUDP;
	
 }
 
 void CKademlia::saveDVNState(void* curNodeMem)
 {
	 struct kad_node_mem_t *thisNode = (struct kad_node_mem_t*)curNodeMem;
	 
	 thisNode->instance = instance;
	 //m_events;
	 thisNode->m_nextSearchJumpStart = m_nextSearchJumpStart;
	 thisNode->m_nextSelfLookup =      m_nextSelfLookup;
	 thisNode->m_nextFirewallCheck =   m_nextFirewallCheck;
	 thisNode->m_nextFindBuddy =       m_nextFindBuddy;
	 thisNode->m_statusUpdate =        m_statusUpdate;
	 thisNode->m_bigTimer =            m_bigTimer;
	 thisNode->m_bootstrap =           m_bootstrap;
	 thisNode->m_running =             m_running;
	 thisNode->bytes_sent =            bytes_sent;
	 thisNode->packets_sent =          packets_sent;
	 //thisNode->m_prefs =               m_prefs;
	 thisNode->m_routingZone =         m_routingZone;
	 thisNode->m_udpListener =         m_udpListener;
	 //UDPIO		myUDP;
	 
 }
 
 */


